SkeldJS allows you to create your own, register, unregister and override your own innernet objects (INOs).
An INO can belong to either the player or the room by default. Only the host can update objects that belong to the room, and each player can only access objects that they own.
The host can also generally update other player's objects, although it depends whether this will have any effect.
An InnerNet Object has 1 or more components, where the first component is classed as the entity and each component can reference each other. You only need to declare INO components, as INOs are handled implicitly as the first component.
Components make up an INO, for all intents and purposes in skeldjs, an INO is just an array of components.
The general skeleton for an INO component object in skeldjs is as follows, although should be extended significantly to provide methods and networking utilities.
export class MyFavouriteComponent<RoomType extends Hostable> extends Networkable<RoomType> {
Deserialize(
reader: HazelReader,
isSpawn: boolean
) {
}
Serialize(
writer: HazelWriter,
isSpawn: boolean
) {
return true;
}
PreSerialize() {
}
async HandleRpc(rpc: BaseRpcMessage) {
}
FixedUpdate(delta: number) {
}
Awake() {
}
Destroy() {
}
}
The two most important methods, Deserialize
and Serialize
determine how your
object will be networked, and you can use these methods to serialize and update
data on all clients. isSpawn
signifies whether this is the first time the object
is being spawned, as you may want to have separate methods for serializing data
for spawns and data updates.
Data for an INO component can be transferred in 3 ways, each with their own purpose:
Data sent when the object is spawned is generally used for initially providing information that isn't necessary to update constantly. For example, the PlayerControl object contains a boolean for whether the player is new to the game or not, but this isn't present in the normal data update.
Sent via the Spawn protocol message when the object is first spawned.
You can serialize this data with the Serialize(writer, isSpawn)
method and
checking for isSpawn
to be true, and you can handle it with the Deserialize(reader, isSpawn)
method.
Usually used for syncing data instead of actual updates, and for more complicated updates in general.
Sent via the Data protocol message at most every fixed update cycle.
You can serialize this data with the Serialize(writer, isSpawn)
method and
checking for isSpawn
to be false, and you can handle it with the Deserialize(reader, isSpawn)
method.
For Serialize(writer, isSpawn)
to be called on your component, you must set your
component's dirtyBit
to a number greater than 0.
Stands for Remote Procedure Call, typically used for individual commands that the client or host can send to perform specific operations. Can modify state of the component. Can also be used for clients to send commands directly to the host, for example the CheckName Rpc.
Can be sent either via the Hostable.broadcast method on the room or with
the Hostable.messageStream, and can be handled with the async HandleRpc(rpc)
method on the INO component.
SkeldJS also provides some useful lifecycle methods for INO components:
PreSerialize()
Called before the serialize method, it allows you to prepare your component for
serialization, such as setting the dirtyBit
to 1
before-hand.
FixedUpdate(delta: number)
Called on every fixed update cycle, and passes a delta
argument indicating the
number of miliseconds passed since the last fixed update, somewhere around 20
.
The time delta can be used to make time-dependent behaviours, such as a ship system like O2 or Reactor counting down.
Awake()
Called as soon as the component is spawned by SkeldJS (note: not when receiving a spawn message). This can be used to introduce the component to the room.
You should not use the constructor to do this component's job, as innernet objects should by default by component stateless from the room.
Destroy()
Called as soon as the component is destroyed by SkeldJS. This is best used to clean up any effects that the component had while in the room.
constructor(
room: RoomType, // The room that the component belongs to
spawnType: SpawnType, // The INO spawn id that the component is for (e.g. 4 if it's a Player component)
netId: number, // The netId of the component
ownerId: number, // The owner of the component
flags: number, // The flags that the component was instantiated with
data?: HazelReader | any // The data to instantiate the component with
) {
super(room, spawnType, netId, ownerid, flags, data);
}
As part of a good and consistent API, your component class should have 4 methods for each action that can be done with your component:
_handleX(rpc: YourRpc)
method used for handling a received RPC._X()
method used for actually executing your action internally in skeldjs_rpcX()
method used to actually network the rpc to the host or other clientsX()
method to use as an API method, to call both _X()
and _rpcX()
.For example, if you had a custom player component that had the ability to change the shape of the player, you might want to have something like:
export enum PlayerShape {
Square,
Circle,
Triangle
}
export class ShapeShifterPlayerControl<RoomType extends Hostable> extends Networkable<RoomType> {
currentShape = PlayerShape.Square;
Deserialize(
reader: HazelReader,
isSpawn: boolean
) {
this.currentShape = reader.uint8();
}
Serialize(
writer: HazelWriter,
isSpawn: boolean
) {
writer.uint8(this.currentShape);
this.dirtyBit = 0;
return true;
}
async HandleRpc(rpc: BaseRpcMessage) {
switch (rpc.messageTag) {
case ShapeShifterRpcTags.SetShape:
await this._handleSetShape(rpc as SetShapeMessage);
break;
}
}
private async _handleSetShape(rpc: SetShapeMessage) {
this._setShape(rpc.shape);
// Emit custom events here
}
private _setShape(shape: PlayerShape) {
this.currentShape = shape;
this.dirtyBit = 1;
}
private _rpcSetShape(shape: PlayerShape) {
this.room.stream.push(
new RpcMessage(
this.netId,
new SetShapeMessage(shape)
)
);
}
setShape(shape: PlayerShape) {
this._setShape(shape);
// Emit custom events here
this._rpcSetShape(shape);
}
}
You can use the room.registerPrefab(number, NetworkableConstructor<Networkable>[])
method to register your INO on the game and to allow it to be spawned when the host
or server requests.
The first parameter is the SpawnType of the INO, you can override any built-in or previously registered INOs.
The second is an array of components in your INO, where the first component acts as the entity and as such is a component of itself.
Generated using TypeDoc